require 'active_unreal/document'
require 'active_unreal/collection'
require 'socket'
require 'json'
require 'active_unreal/apiprotocol'


module UnrealDB
  class Connection
    def initialize(host, port = LISTENER_PORT)
      @host = host
      @port = port
      @socket = nil
    end
    
    def connect
      return true if @socket != nil
      begin
        @socket = TCPSocket::new(@host, @port)
        return true
      rescue Exception => e
        puts e.class.to_s + " : " + e.message
        return false
      end
    end
    
    def close
      data = API_CLOSE_CONNECTION.to_s + ";0;0"
      @socket.puts(data)
      @socket.close
      @socket = nil
    end
    
    def get_cursor
      # Retorna um cursor para a conexao
      Cursor.new(@socket)
    end
  end
  
  #Classe responsavel pelas operacoes que podem ser realizadas no banco
  class Cursor
    def initialize(connection)
      @connection = connection
      @response_data_size = 0
      @response_data = ""
    end
    
    def get_document(document_id)
      flush_vars
      
      return nil if @connection == nil
      return nil if !send_basic_request(API_GET_DOCUMENT, document_id, 0)
      return nil if !receive_basic_response
      return nil if @response_data_size == 0
      return nil if !receive_response_data
      data = JSON.parse(@response_data)
      doc = Document.new(document_id)
      doc << data 
      return doc
    end
  
    def insert_document(document)
      flush_vars
      if document.is_a?(Document)
        json = document.to_json
        #json = JSON.generate(document.to_h)
        
        return false if @connection == nil
        return false if !send_basic_request(API_INSERT_DOCUMENT, document['document_id'], json.length)
        return false if !send_document(json.length, json)
        return false if !receive_basic_response
    
        return true
      end
      return false
    end
  
    def update_document(document)
      flush_vars
      if document.is_a?(Document)
        json = document.to_json
        #json = JSON.generate(document.to_h)
        return false if @connection == nil
        return false if !send_basic_request(API_UPDATE_DOCUMENT, document['document_id'], json.length)
        return false if !send_document(json.length, json)
        return false if !receive_basic_response
  
        return true
      end
      return false
    end
  
    def remove_document(document_id)
      flush_vars
  
      return false if @connection == nil
      return false if !send_basic_request(API_REMOVE_DOCUMENT, document_id, 0)
      return false if !receive_basic_response
  
      return true
    end
    
    def query(qry)
      flush_vars
      
      raise (Exception, "Invalid Document ID, please remove ';'") if qry.match(';')
      
      return false if @connection == nil
      return false if !send_basic_request(API_QUERY, qry,0)
      return nil if !receive_basic_response
      return nil if @response_data_size == 0
      return nil if !receive_response_data
      #@response_data = (@response_data.is_a?(Hash) ? @response_data : {qry.split("=>")[-1] => @response_data})
      #json = JSON.generate(@response_data)
      data = JSON.parse(@response_data)
      doc = Document.new(data['document_id'])
      doc << data 
      return doc
    end
      
    def insert_collection(collection)
      result = []
      collection.each do |doc|
        result << [doc['document_id'], insert_document(doc)]
      end
      return result
    end
  private
    def send_basic_request(oper_id, document_id, document_size)
      packed = oper_id.to_s + ";" + document_id + ";" + document_size.to_s
  
      begin
        @connection.puts(packed)
      rescue
        return false
      end
  
      return true
    end
  
    def send_document(document_size, document)
      sent = 0
  
      while sent < document_size
        begin
          result = @connection.send(document[sent..-1], 0)
        rescue
          return false
        end
  
        return false if result == nil
  
        sent += result
      end
  
      return true
    end
  
    def receive_basic_response
      begin
        data = @connection.readline
      rescue
        return false
      end
  
      return false if data == nil
  
      array = data.split(";")
  
      @status = Integer(array[0])
      @response_data_size = Integer(array[1])
  
      return false if @status != API_REQ_OK
  
      return true
    end
  
    def receive_response_data
      received = 0
  
      while received < @response_data_size
        remaining = @response_data_size - received
  
        begin
          result = @connection.read(remaining)
        rescue Exception => e
          puts "Erro no read" + e.message
          return false
        end      
  
        return false if result == nil
  
        @response_data += result
        received += result.length
      end
  
      return true
    end
    
    def flush_vars
      @response_data_size = 0
      @response_data = ""
    end

  end
end
